package com.lime49.lockcrypt;

import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.JOptionPane;
import javax.xml.parsers.*;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * Connects to a C# helper application to automatically type data
 */
public class AutoTyper {
    private ILanguageParent parent;
    
    /**
     * initializes a new AutoType
     * @param parent The parent dialog
     */
    public AutoTyper(ILanguageParent parent) {
        this.parent = parent;
    }
    
    /**
     * Starts the AutoType helper.
     */
    public void launchAutoTyper() {
        if(!canConnect(true)) {
            String jarLocation = System.getProperty("user.dir");
            String exeLocation = jarLocation.endsWith("/") ? jarLocation+"/LockCrypt-AutoType.exe" : jarLocation+"/LockCrypt-AutoType.exe";
            try {
                Runtime.getRuntime().exec(exeLocation);
            } catch(Exception e) {
                try { // might be running locally
                    Runtime.getRuntime().exec("C:\\Users\\Echilon\\Documents\\Coding\\LockCrypt-AutoType\\LockCrypt-AutoType\\bin\\Release\\LockCrypt-AutoType.exe");
                } catch(Exception ex) {
                    System.out.println(parent.getLanguage().getString("ERROR")+" #204: "+parent.getLanguage().getString("ERR_LAUNCHAUTOTYPE")+": "+exeLocation+"\n"+e.getMessage());
                }
            }
        }
    }
    
    /**
     * Tries to connect to the AutoType helper application, shows an error if unable to connect.
     * @return True if the helper responds, otherwise false;
     */
    public boolean canConnect() {
        return canConnect(false);
    }
    
    /**
     * Tries to connect to the AutoType helper application.
     * @param suppressError False if a message should be shown if connection fails, otherwise True
     * @return True if the helper responds, otherwise false;
     */
    public boolean canConnect(boolean suppressError) {
        Socket sock = null;
        boolean isAlive = false;
        try {
            sock = new Socket(InetAddress.getByName("localhost"), 1557);
            sock.setSoTimeout(150);
            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
            out.write("<method name=\"ping\"/>\n");
            out.flush();
            int c = 0;
            StringBuffer buffer = new StringBuffer();
            while((c = in.read()) != -1){
                buffer.append((char)c);
            }
            if(buffer.toString().equals("pong")) {
                isAlive = true;
            }
            out.close();
            in.close();
        } catch(Exception ignore) { 
        } finally {
            if(sock != null) {
                try {
                    sock.close();
                } catch(Exception ignore) { }
            }
        }
        if(!isAlive && !suppressError) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE"),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
        return isAlive;
    }
    
    /**
     * Finds the captions of currently running desktop windows
     * @return The captions of currently running desktop windows
     */
    public String[] getWindows() {
        Socket sock = null;
        ArrayList<String> windows = new ArrayList<String>();
        try {
            sock = new Socket(InetAddress.getByName("localhost"), 1557);
            sock.setSoTimeout(150);
            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            out.write("<method name=\"findwindows\"/>\n");
            out.flush();
            
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(sock.getInputStream());
            doc.getDocumentElement().normalize();
            NodeList windowNodes = doc.getElementsByTagName("window");
            int numWindows = windowNodes.getLength();
            
            for(int i=0;i<numWindows;i++) {
                Node window = windowNodes.item(i);
                NamedNodeMap attrs = window.getAttributes();
                if(attrs.getLength() > 0) {
                    windows.add(attrs.item(0).getNodeValue());
                }
            }
            out.close();
        } catch(SocketTimeoutException to) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE"),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } catch(Exception ex) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE")+":\n"+ex.getLocalizedMessage(),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } finally {
            if(sock != null) {
                try {
                    sock.close();
                } catch(Exception ignore) { }
            }
        }
        String[] windowAry = new String[windows.size()];
        for(int i=0;i<windowAry.length;i++) {
            windowAry[i] = windows.get(i);
        }
        return windowAry;
    }
    
    /**
     * Starts recording keystrokes and mouse actions
     */
    public void recordAction() {
        Socket sock = null;
        try {
            sock = new Socket(InetAddress.getByName("localhost"), 1557);
            sock.setSoTimeout(150);
            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            out.write("<method name=\"recordaction\"/>\n");
            out.flush();
        } catch(SocketTimeoutException to) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE"),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } catch(Exception ex) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE")+":\n"+ex.getLocalizedMessage(),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } finally {
            if(sock != null) {
                try {
                    sock.close();
                } catch(Exception ignore) { }
            }
        }
    }
    
    /**
     * Returns the actions recorded since recording started
     * @return The actions recorded since recording started
     */
    public String getRecordedAction() {
        StringBuffer buffer = new StringBuffer();
        Socket sock = null;
        try {
            sock = new Socket(InetAddress.getByName("localhost"), 1557);
            sock.setSoTimeout(150);
            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            out.write("<method name=\"getaction\"/>\n");
            out.flush();
                
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(sock.getInputStream());
            doc.getDocumentElement().normalize();
            NodeList actionNodes = doc.getElementsByTagName("action"),
                     errorNodes = doc.getElementsByTagName("error");
            int numActions = actionNodes.getLength(),
                numErrors = errorNodes.getLength();
            if(numErrors > 0) {
                throw new Exception(errorNodes.item(0).getNodeValue());
            } else {
                String actionType;
                Node action;
                NamedNodeMap attrs;
                for(int i=0;i<numActions;i++) {
                    action = actionNodes.item(i);
                    attrs = action.getAttributes();
                    if(attrs.getLength() > 0) {
                        //System.out.println(attrs.item(0).getNodeValue()+":"+action.getChildNodes().item(0).getNodeValue());
                        actionType = attrs.item(0).getNodeValue().toLowerCase();
                        if(actionType.equals("keys")) {
                            buffer.append("{Keys:"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("leftbuttonup")) {
                            buffer.append("{MouseUp:L,"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("leftbuttondown")) {
                            buffer.append("{MouseDown:L,"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("leftclick")) {
                            buffer.append("{MouseClick:L,"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("rightbuttonup")) {
                            buffer.append("{MouseUp:R,"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("rightbuttondown")) {
                            buffer.append("{MouseDown:R,"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("rightclick")) {
                            buffer.append("{MouseClick:R,"+action.getChildNodes().item(0).getNodeValue()+"}");
                        } else if(actionType.equals("MouseWheel")) {
                            //buffer.append("{MouseWheel:"+action.getChildNodes().item(0).getNodeValue()+"}");
                        }
                    }
                }
            }
            out.close();
        } catch(Exception ex) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE")+":\n"+ex.getLocalizedMessage(),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } finally {
            if(sock != null) {
                try {
                    sock.close();
                } catch(Exception ignore) { }
            }
        }
        return buffer.toString();
    }
    
    /**
     * Automatically performs an action
     * @param target The target window caption (* = wildcard)
     * @param config The actions to perform
     */
    public void doAutoType(String target, String config) {
        Socket sock = null;
        try {
            sock = new Socket(InetAddress.getByName("localhost"), 1557);
            sock.setSoTimeout(150);
            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            out.write("<method name=\"doautotype\"><param name=\"target\">"+target+"</param><param name=\"config\">"+config+"</param></method>\n");
            out.flush();
            out.close();
        } catch(Exception ex) {
            JOptionPane.showMessageDialog(parent.getParentWindow(), parent.getLanguage().getString("ERR_CONNECTAUTOTYPE")+":\n"+ex.getLocalizedMessage(),  parent.getLanguage().getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } finally {
            if(sock != null) {
                try {
                    sock.close();
                } catch(Exception ignore) { }
            }
        }
    }
    
    /**
     * Notifies the utility to exit
     */
    public void shutdown() {
        Socket sock = null;
        try {
            sock = new Socket(InetAddress.getByName("localhost"), 1557);
            sock.setSoTimeout(150);
            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            out.write("<method name=\"shutdown\" />\n");
            out.flush();
            out.close();
        } catch(Exception ignore) {
        } finally {
            if(sock != null) {
                try {
                    sock.close();
                } catch(Exception ignore) { }
            }
        }
    }
}
